#!/usr/bin/env python
# Copyright (c) 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Client-side script to send local git changes to a tryserver.

It pushes the local feature branch to a private try-ref on the central repo
and posts a description of the job to an appengine instance, where it will get
picked up by the buildbot tryserver itself.
"""

from __future__ import print_function

import json
import os
import subprocess
import sys
import urllib


def DieWithError(msg):
  """Prints the message to stderr and exits."""
  print(msg, file=sys.stderr)
  sys.exit(1)


def RunGit(*args, **kwargs):
  """Runs the given git command with arguments, or dies.

  Passes the given kwargs (e.g. cwd or env) through to subprocess."""
  cmd = ('git',) + args
  try:
    return subprocess.check_output(cmd, **kwargs).strip()
  except subprocess.CalledProcessError as e:
    DieWithError('Command "%s" failed.\n%s' % (' '.join(cmd), e))


def EnsureInGitRepo():
  """Quick sanity check to make sure we're in a git repo."""
  if not RunGit('rev-parse', '--is-inside-work-tree') == 'true':
    DieWithError('You don\'t appear to be inside a git repository.')


def GetCodeReviewSettings():
  """Reads codereview.settings and returns a dict of settings."""
  top_dir = RunGit('rev-parse', '--show-toplevel')
  this_dir = os.getcwd()
  assert this_dir.startswith(top_dir), (top_dir, this_dir)

  settings_file = os.path.join(this_dir, 'codereview.settings')
  while not os.path.isfile(settings_file):
    this_dir = os.path.split(this_dir)[0]
    if not this_dir.startswith(top_dir):
      DieWithError('Unable to find codereview.settings in this repo.')
    settings_file = os.path.join(this_dir, 'codereview.settings')

  settings = {}
  with open(settings_file, 'r') as f:
    for line in f.readlines():
      if line.startswith('#'):
        continue
      k, v = line.split(':', 1)
      settings[k.strip()] = v.strip()
  return settings


def PushBranch():
  """Pushes the current local branch to a ref in the try repo.

  The try repo is either the remote called 'try', or 'origin' otherwise.
  The ref is '/refs/tryjobs/<username>/<local branch>-<short hash>.

  Returns the ref to which the local branch was pushed."""
  username = RunGit('config', '--get', 'user.email').split('@', 1)[0]
  branch = RunGit('symbolic-ref', '--short', '-q', 'HEAD')
  commit = RunGit('rev-parse', branch)[:8]
  remotes = RunGit('remote').splitlines()
  if not all((username, branch, commit, remotes)):
    DieWithError('Unable to get necessary git configuration.')

  remote = 'try' if 'try' in remotes else 'origin'
  ref = 'refs/tryjobs/%s/%s-%s' % (username, branch, commit)

  RunGit('push', remote, '%s:%s' % (branch, ref))
  return ref


def MakeJob(project, jobname, ref):
  """Creates a job description blob."""
  email = RunGit('config', '--get', 'user.email')
  repository = RunGit('config', '--get', 'remote.origin.url')
  job = {
      # Fields for buildbot sourcestamp.
      'project': project,
      'repository': repository,
      'branch': ref,
      'revision': 'HEAD',
      # Fields for buildbot builder factory.
      'buildername': jobname,
      'recipe': project,
      # Other useful fields.
      'blamelist': [email],
  }
  return json.dumps(job)


def PostJob(server, project, job):
  """POSTs the job description blob to the tryserver instance."""
  if not server.startswith('https://'):
    DieWithError('Server URL must be https.')
  url = '%s/%s/push' % (server, project)
  data = urllib.urlencode({'job': job})
  try:
    conn = urllib.urlopen(url, data)
  except IOError as e:
    DieWithError(e)
  response = conn.getcode()
  if response != 200:
    DieWithError('Failed to POST. Got: %d' % response)


def Main(_argv):
  """Main entry point."""
  # Sanity check.
  EnsureInGitRepo()

  # Get some settings.
  settings = GetCodeReviewSettings()
  server = settings.get('TRYSERVER_HTTP_HOST')
  project = settings.get('TRYSERVER_PROJECT')
  jobnames = settings.get('TRYSERVER_JOB_NAME')
  if not all((server, project, jobnames)):
    DieWithError('Missing configuration in codereview.settings.')

  # Do the heavy lifting.
  ref = PushBranch()
  for jobname in jobnames.split(','):
    job = MakeJob(project, jobname, ref)
    PostJob(server, project, job)


if __name__ == '__main__':
  sys.exit(Main(sys.argv))
